home *** CD-ROM | disk | FTP | other *** search
/ Games of Daze / Infomagic - Games of Daze (Summer 1995) (Disc 1 of 2).iso / x2ftp / msdos / source / rpg_scrl / programr.txt < prev    next >
Text File  |  1994-09-20  |  29KB  |  618 lines

  1.                           PROGRAMMER'S   NOTES
  2.                    An Explanation of How it All Works
  3.                              For Begginers
  4.  
  5. Table Of Contents
  6.       Purpose
  7.       Audience
  8.       Disclaimer
  9.       Graphics Basics (Pixels and Bytes)
  10.       Assembler - Basics
  11.       The Virtual Screen
  12.       Assembler - Drawing Tiles
  13.       The Vertical Retrace
  14.       Assembler - Showing a Virtual Screen
  15.       Interlude: Optimizing Assembler - Speed Speed Speed
  16.       Assembler - Drawing Sprites
  17.       How I Got These Pictures
  18.       Why Pascal
  19.       Why I Wrote This
  20.  
  21. Purpose
  22. =======
  23. This is a small tutorial for those not familiar with basic game
  24. graphics. It shows how tiles/sprites/little pictures can be drawn
  25. using a little bit of assembler language.
  26.  
  27. Audience
  28. ========
  29. Many experienced programmers probably already know these things, so
  30. this is really targeted to people who aren't exactly sure how some
  31. of these things work.
  32.  
  33. Disclaimer
  34. ==========
  35. Hey, I'm no genius, so some of this might be wrong and it is
  36. most certainly inefficient. But I do know it works. The sample
  37. code that comes with this was written in one afternoon (actually, a
  38. programmer afternoon, which is 6pm to 2am) and was debugged in another.
  39. I'm going to improve it eventually, just like I'll eventually add
  40. comments (I don't feel too bad as I comment a bit more than most
  41. programmers). Hopefully there's enough substance here to help a few
  42. people out.
  43.  
  44. Graphics Basics (Pixels, Bytes and Mode 13h)
  45. ============================================
  46. The video screen you're staring at is made of of thousands of tiny
  47. pixels. A pixel is a small dot, the smallest element a program
  48. can use. It will always be one color. But which color? That gets
  49. set by the program running. How many choices does it have? That
  50. depends on how many bits are assigned to a pixel. A bit is
  51. either the number 0 or 1. On IBMs, bits are grouped in packs
  52. of 8 called bytes (the smallest piece of information a computer
  53. can directly affect). For video modes like text mode (your normal
  54. black and white screen), you can store 8 pixels in a byte, and
  55. since each pixel only gets, therefore, 1 bit, it only has the
  56. choice of two colors (0 or 1, often white or black or on older
  57. monitors dark green or light green). How do you change an
  58. individual pixel if your program can only address bytes -
  59. don't ask, you don't want to know (it involves masking, logical
  60. operators or just plain hard coding the image). The old CGA
  61. monitors had pixels that had 2 bits (4 colors), EGA had
  62. 4 bits (2 to the 4rth power, or 16 colors) and VGA has
  63. 8 bits (256 colors).
  64.  
  65. The IBM PCs, by nature, have planar video modes. Now that
  66. you've heard the term, forget it - you wouldn't like it. It
  67. involves splitting the bits of a pixel over several bytes,
  68. so to get a color of one pixel that's 4 bits deep, you might
  69. have to obtain the 7th bit of bytes 1,2,3 and 4 in the video
  70. memory. Yuch. (There are advantages to this, but we'll just
  71. ignore them here).
  72.  
  73. The PC does offer one video mode which is linear - mode 13h
  74. (that's 13 hex, or 19 in human numbers). It creates a screen
  75. that is 320x200 and each pixels has 8 bits, or 256 colors.
  76. Nicely enough, 8 bits is also the size of a full byte, so we can
  77. modify the colors without nasty masking and such.
  78.  
  79. Since mode 13h is linear, it means the pixel next to you also
  80. happens to be the bit (or in this case, byte) in the video
  81. memory next to you. The mode 13h screen is essentially a large
  82. array (320x200 = 64,000) of bytes stored at the place in memory
  83. your computer reserves for the interface to your graphics card
  84. (this is 0A000:0000h for those curios - it's a different
  85. address for other video modes, especially normal and
  86. monochrome text modes which are at b800:0000 and b000:0000).
  87. So the screen is really just
  88.         Screen = array [1..64000] of byte
  89.  
  90. If you wanted, and many have done this, for a basic way to change
  91. the colors of pixels, you could declare an array at that offset
  92. (Screen = array [1..64000] of byte absolute $a000) and just change
  93. the contents directly (Screen [y*320+x]:=0; which is black).
  94. (Note: while this is great in a sloppy language like C, in Pascal,
  95. you cannot declare an array like the above because of range
  96. checking considerations - the real code is Screen = array[0..0] of byte
  97. and the PutPixel function is {$R-}Screen[y*320+x]:=color{$R+} ).
  98.  
  99. In case you noticed, to get to the proper place in the array, you
  100. multiply the y coordinate by the length of the row (320 here) and
  101. add the x coordinate, so the coordinates 1,2 is Screen[641].
  102.  
  103.  
  104. In case your wondering, mode X, which seems popular now (for good
  105. reason), is a planar video mode, and much more complex to program. I
  106. won't cover it here.
  107.  
  108. Assembler - Basics
  109. ==================
  110. The heart of all assembler routines is the movs function - move string.
  111. It comes in many variants - movsb, movsw and movsd (for byte, word -two
  112. bytes-, or double word -four bytes-). So in our situation, this
  113. will draw 1 pixel, 2 pixels or four pixels in one fell swoop.
  114.  
  115. How does movs know where to get the data to copy and where to copy
  116. to? It uses certain built-in assembler variables - es,di,ds and si.
  117. Each of these is a word (2 bytes or 16 bits). The place to copy from
  118. is ds:si and the place to copy to is es:di (the : means put them
  119. together - it's segment offset format, a dos segmented memory
  120. model that plenty of people complain about).
  121.  
  122. And how do figure out what numbers to put in there? Well, in
  123. assembler, the mov command is like the pascal := or the C =
  124. but that only helps so much because es and ds (extra segment
  125. and data segment) are wierd - you can't just copy any number
  126. into these places, they can only have values moved into them
  127. with mov from an assembler register (seen below). So you have
  128. another option - les and lds (load es and ds respectively).
  129. You can use these with your variables. So say you have
  130. an picture stored, byte for byte, in something like
  131. Picture = array [1..32,1..32] of byte;  Fred : Picture.
  132. You would say copy from my picture by   lds si,Picture
  133. which will get the segment and offset of wherever it
  134. is the program places your picture in memory and put it
  135. in ds:si.
  136.  
  137. Assembler offers 4 basic registers - ax,bx,cx, and dx. Each is
  138. a word long. Sometimes they have special purposes - for example,
  139. cx is used by many assembler commands as a counter. You can load
  140. anything into these with the mov command.
  141.  
  142. So you wanna draw something, right? Let's copy one pixel from
  143. the top left corner of our picture (in format array[1..32,1..32] of
  144. byte) into the top left corner of the screen. We'd do:
  145.         mov ax,a000h      ; where the screen starts
  146.         mov es,ax         ; load it into es
  147.         mov di,0          ; set the rest to 0 so it's a000:0000
  148.  
  149.         lds si,MyPicture  ; get the coordinate of our picture
  150.  
  151.         movsb             ; and copy our pixel
  152.  
  153. Note: the movs command automatically increments the source and
  154. destination pointers. So after this call, es:di, which was
  155. a000:0000 is now a000:0001. A call to movsw would have added two
  156. and a call to movsd (for 386s only) would have added four
  157. (a000:0002 and a000:0004 respectively). So if you called movsb
  158. again it would copy the next pixel in your picture to the next spot on
  159. the screen.
  160.  
  161. One more thing you should know - many compilers have limitations on
  162. the kind of assembler you can use inside it. For example, my copy of
  163. Turbo Pascal and Borland C only allow me to use 286 instructions, not
  164. 386 instructions. It's 386 instructions that allow things like movsd and
  165. eax,,ebx,etc (32-bit, or double-word, registers). And my compiler
  166. doesn't even use 286 instructions by default - I had to go to copiler
  167. options to even get that (it starts with 8086 instructions, the next
  168. step down).
  169.  
  170. The Virtual Screen
  171. ==================
  172. It is often beneficial to have a "scratch" screen. In arcade games
  173. (which are also tile-based by the way) they typically draw in layers -
  174. 1)Draw Background 2)Draw monsters and heroes and objects 3)Draw things
  175. that obscure the hero (like walking behind a cliff or tree and covering
  176. the hero). You probably don't want to show all that as you perform it.
  177. It's easier to just take your time drawing on an offscreen buffer and
  178. copying it when it's done.
  179.  
  180. So what's an offscreen buffer? Just like the screen, it's an array. And
  181. how big is it? Well, if you use it to draw your whole screen, it's going
  182. to be [1..64000] of byte in mode 13h. In the example program, only a
  183. small portion (the travel map) is drawn, so make it as big as you need
  184. to.
  185.  
  186. Assembler - Drawing Tiles
  187. =========================
  188. Here's where you learn how to draw a picture in an offscreen buffer. It
  189. assumes you have a picture. I use a tile type that's 32 pixels x 32.
  190. It's stored, pixel by pixel, top left to bottom right, in an
  191. array[1..32,1..32] of byte. The screen here is 9 tiles by 7 tiles (you
  192. can figure the pixels yourself - 9*32 x 7*32). The procedure is as
  193. follows:
  194.  
  195. Procedure PlaceTileInBuffer (PixelX,PixelY:word; var Pic:icon32); assembler;
  196.   const
  197.        WordLength = TileWidth div 2;
  198.    Asm
  199.         (* figure pixel offset in buffer *)
  200.         mov  ax,BufWidth        (* get length of buffer - 9*32 *)
  201.         mul  PixelY             (* drop down to the line you want *)
  202.                                 (* This automatically multiples the *)
  203.                                 (* Y-val by ax, which is the X-val *)
  204.         add  ax,PixelX          (* gives (Y*width)+x *)
  205.  
  206.         (* preserve data segment pointer *)
  207.         (* Other things used this before you got it and will use *)
  208.         (* it again when you're done with it, so save the value *)
  209.         (* by moving it someplace we won't hurt it or alternately *)
  210.         (* you could save it with push dx and restore with pop dx *)
  211.         mov  dx,ds
  212.  
  213.         (* Copy to where? *)
  214.         les  di,buffer          (* buffer is your offscreen buffer *)
  215.         mov  di,ax              (* move to the pixel in the buffer *)
  216.                                 (* you want to start at *)
  217.  
  218.         (* Copy from where? *)
  219.         lds  si,Pic             (* load your picture *)
  220.  
  221.         (* Copy Data *)
  222.         mov  bx,TileHeight      (* here, 32 pixels *)
  223.    @@CopyRowLoop:
  224.         mov  cx,WordLength      (* how many words long is the row? *)
  225.         push di                 (* save offset *)
  226.         rep  movsw              (* copy cx words to the buffer *)
  227.         pop  di                 (* restore offset *)
  228.         add  di,BufWidth        (* go to next line *)
  229.         dec  bx                 (* finished that row already *)
  230.         jnz  @@CopyRowLoop      (* if there are any more rows in bx *)
  231.                                 (* go ahead and do this again *)
  232.  
  233.         (* OK, all done, so quit *)
  234.         mov  ds,dx              (* restore data segment pointer *)
  235.    End;
  236.  
  237. Ok, so what's it do? Basically, the code is something like
  238.         Figure where in the buffer to draw this
  239.         Specify where you're copying to
  240.         Specify where you're copying from
  241.         For i:= 1 to Number Of Rows do
  242.             Copy Row to Buffer
  243.  
  244. We used WordLength (16 words, since it was 32 bytes) so we could copy
  245. using movsw, which is supposed to be quicker than movsb. If this had
  246. been 386-optimized code, we would have done mov cx,8 rep movsd. And if
  247. you haven't figured it out, rep is repeat, which says do the following
  248. commands cx number of times (after it's done cx will be equal to 0).
  249.  
  250. Now why did we copy row by row? It's because we only want to copy to a
  251. certain place (we're only copying one tile to a whole screen full
  252. of tiles). Imagine
  253.  
  254.         -------------------------------
  255.         |    |    |    |    |    |    |
  256.         -------------------------------
  257.         |    |    |    |    |    |    |
  258.         -------------------------------
  259.         |    |    |000 |    |    |    |
  260.         -------------------------------
  261.         |    |    |    |    |    |    |
  262.         -------------------------------
  263.         |    |    |    |    |    |    |
  264.         -------------------------------
  265.  
  266. Ok, it's not drawn to scale, but you get the point - the lines are
  267. your virtual screen and the 000 is the picture you just copied. You
  268. figure the x,y coords here (if the array is linear, like [1..64000] then
  269. the first 320 bytes are the first row, so the beggining of the second
  270. row is 321 and the beggining of the third row is 641, and the third
  271. pixel on the second row is 643, or in other words, (Y*BufferWidth)+X).
  272. So if es:di is pointing to, say, 0000:0010, which is the beggining of
  273. that tile, then you'd save that number (push di), copy 32 pixels (which
  274. means es:di is now 0042), restore the beggining of the picture (pop di),
  275. and move to the next line (add BufferWidth, or 320, making it 0330). If
  276. we didn't do this, we'd end up copying a tile that's 1 pixel high and
  277. really really long (32*32, which actually would wrap for about 3 lines).
  278.  
  279. And what, pray tell, is meant by
  280.    @@CopyRowLoop:
  281.         dec  bx
  282.         jnz  @@CopyRowLoop
  283. Well, we're going to use bx to store the number of rows (32). BX is used
  284. by the jump commands (jmp,jnz,jz,je,jne,etc). Jnz is jump if not zero,
  285. and dec bx means decrease (subtract 1). The @@: is pascalish for local
  286. label, which means jump to here (yes, it's a goto statement).
  287.  
  288. So we say bx=32, copy a row, subtract one from bx, if bx does not equal
  289. 0 (if we have any rows left) go back and do it again. Alternately,
  290. rather than saving the starting pixel, we could have just added
  291. BufWidth-TileWidth. Whichever you prefer.
  292.  
  293. We could also have used a loop statement (loop @@CopyEtc:) but it checks
  294. cx, which we were using for something else (the number of bytes to copy
  295. in rep movsw).
  296.  
  297. So is this beggining to make sense?
  298.  
  299. Assembler - Showing a Virtual Screen
  300. ====================================
  301. So how do you show a virtual screen?
  302.  
  303. Well, if it depends on the size of the buffer. If the buffer is not the
  304. same size as the screen (which ours isn't) you pretend the buffer is
  305. just a big tile and copy it to the screen. Remember, you load the screen
  306. by doing
  307.         mov ax,0a000h
  308.         mov es,ax
  309.         xor di,di    ; this says di=0, which is faster than mov di,0
  310.  
  311. And if it's the same size as the screen? Even better. Then we don't have
  312. to do any of this complicated adjusting for the next row stuff. It's
  313. just:
  314.      push ds                  (* save the data segment *)
  315.  
  316.      lds si,Buffer;           (* load scratch page  *)
  317.      mov ax,Screen_Offset;    (* load screen coords *)
  318.      mov es,ax                (* es = 0a000h *)
  319.      xor di,di                (* di = 0 *)
  320.      mov cx,32000             (* copy 32,000 words / 64,000 bytes *)
  321.      rep movsw                (* and do the copy *)
  322.  
  323.      pop ds                   (* restore the data segment *)
  324.  
  325. The Vertical Retrace
  326. ====================
  327. So you almost have what you need - except that matter about the vertical
  328. retrace. The way a monitor works, a small beam inside the monitor shoots
  329. at the screen (the pixels/dot pitch, which are small pieces of phosphor)
  330. going from left to right, top to bottom (except on interlaced monitors
  331. which skips every other line and comes back for them later).
  332.  
  333. When the beam strikes the phosphorus, it sets it to the color it has in
  334. it's memory. So what happens when you get unlucky and start drawing when
  335. the screen is halfway through the vertical retrace (that is, halfway
  336. through redrawing)? You draw only half of your picture until the next
  337. pass through. Which looks dorky. So what you need to do is wait until
  338. the vertical retrace is done, then copy your data to the screen -
  339. quickly. If you monitor refreshes 70 times a second, you have 1/70th of
  340. a second to copy your data in. That's why it's nice for the copy to
  341. screen routine to be fast (and why we use virtual screens to put things
  342. together before we finally blit them to the screen).
  343.  
  344. And how do we do it?
  345.      (* Wait for Vertical Retrace *)
  346.      cli
  347.      mov dx,3DAh
  348. @@label1:
  349.      in al,dx
  350.      and al,08h
  351.      jnz @@label1
  352. @@label2:
  353.      in al,dx
  354.      and al,08h
  355.      jz  @@label2
  356.      sti
  357.      (* End Check for Retrace *)
  358. How's it work? You don't want to know - trust me. It turns off certain
  359. interrupt handlers then makes calls to the VGA ports (3DAh) and tests
  360. the results. All you need to know is that it works. This code is
  361. available from many ftp sites and numerous books (like the Programmer's
  362. Guide to the EGA/VGA, very good if you're the type who likes reading
  363. dictionaries).
  364.  
  365. Interlude: Optimizing Assembler - Speed Speed Speed
  366. ===================================================
  367. Ok, a few assembler rules. You want as few lines as possible, although
  368. it's important to keep in mind that some instructions are slower than
  369. others. For example xor ax,ax is slower than mov ax,0 (don't know what
  370. and, or and xor do? They're logical operators. Given two conditions, say
  371. It Is Raining and My Dog Is White, and returns true (1) if both are
  372. true, or returns true if either is true, and xor returns true if only
  373. one is true. So for us, if you ANDed the number 101 and 011, the answer is
  374. 001 - only the last digit is true (1) in each number. If it had been
  375. ORed, the number would have been 111, and had it been XORed, the answer
  376. would have been 110. Any number XORed to itself is always all false,
  377. that is 00000000).
  378.  
  379. So where can we save some room? Movsw is faster than movsb, and movsd is
  380. faster than movsw. That'll be important in the next section, as when we
  381. copy sprites (anything with a transparent background) we can only use
  382. movsb.
  383.  
  384. You can also try unrolling your loops. If you're copying two rows,
  385.         mov  bx,TileHeight
  386.    @@CopyRowLoop:
  387.         mov  cx,WordLength
  388.         push di
  389.         rep  movsw
  390.         pop  di
  391.         add  di,BufWidth
  392.         dec  bx
  393.         jnz  @@CopyRowLoop
  394. is slower than
  395.         (* copy one row *)
  396.         mov  cx,WordLength
  397.         push di
  398.         rep  movsw
  399.         pop  di
  400.         add  di,BufWidth
  401.         (* copy second row *)
  402.         mov  cx,WordLength
  403.         push di
  404.         rep  movsw
  405.         pop  di
  406.         add  di,BufWidth
  407. That's because jump and loop statements (like jnz) are slow. In the
  408. latter example you're doing the same basic work without three of the
  409. lines (mov bx,TileHeight  dec bx  jnz @@CopyRowLoop), which means three
  410. less lines to execute.
  411.  
  412. So what's the catch (yes, there is one) - this isn't very portable,
  413. which is a standard rule of assembler : specific routines are faster
  414. than generic ones. If you know your icons are 32x32 and your buffer is
  415. 320x200, you can code
  416.         mov cx,16
  417.         rep movsw
  418.         add di,288
  419.         (repeat 31 times more)
  420. The problem is, short of long, ugly code, is that this routine is no
  421. good for icons 16x16. For that, you'd write another routine (same code
  422. but different magic numbers - mov cx,8 add di,304). But if you're
  423. willing to do that, you can speed things up a bit. But be forewarned -
  424. if you buy or use a library from someone else (say the BGI functions
  425. that come with Borland products), chances are their blitting functions
  426. (functions to copy rectangular areas like sprites) are slower because of
  427. all the extra overhead. And lord forbid if those functions serve more
  428. than one video mode (like EGA and VGA).
  429.  
  430. There are other places where code can be optimized, where certain
  431. statements are faster than others (on certain computers) but I don't
  432. know enough about assembler, frankly, to tell you what they are.
  433.  
  434. Assembler - Drawing Sprites
  435. ===========================
  436. Sprites are like tiles, except they can do something nifty - they don't
  437. look like squares. Why? Because in our code, we specify a color that we
  438. will not draw. Examine the following:
  439.   const
  440.        WordLength = TileWidth div 2;
  441.    Asm
  442.         (* figure pixel offset in buffer *)
  443.         mov   ax,BufWidth        (* we've seen this before, right? *)
  444.         mul   PixelY
  445.         add   ax,PixelX          (* gives (Y*width)+x *)
  446.  
  447.         (* preserve data segment pointer *)
  448.         push  es                 (* I'm not sure es is so important *)
  449.         push  ds                 (* but this sure is *)
  450.  
  451.         (* Copy to where? *)
  452.         les   di,buffer          (* point to our virtual screen *)
  453.         mov   di,ax
  454.  
  455.         (* Copy from where? *)
  456.         lds   si,Pic             (* point to our sprite *)
  457.  
  458.         (* Copy Data - Don't draw black (color 0) pixels *)
  459.         mov   bx,TileHeight
  460.    @@CopyRowLoop:
  461.         mov   cx,TileWidth       (* how many words long is the row? *)
  462.         push  di                 (* save offset *)
  463.    @@PutPixel:
  464.         mov   ax,[ds:si]         (* retrieve value in ds:si        *)
  465.         cmp   ax,0               (* is this a black (#0) pixel?    *)
  466.         je    @@SkipPixel        (* if so, skip it (goto SkipPixel *)
  467.         movsb                    (* copy cx words to the buffer    *)
  468.         loop  @@PutPixel         (* keep looping until cx=0        *)
  469.  
  470.         (* Move to Next Row *)
  471.    @@EndOfRow:
  472.         pop   di                 (* restore offset *)
  473.         add   di,BufWidth        (* go to next line *)
  474.         dec   bx                 (* finished that row already *)
  475.         jnz   @@CopyRowLoop      (* if there are any more rows in bx *)
  476.                                  (* go ahead and do this again *)
  477.         jmp   @@Done
  478.  
  479.    @@SkipPixel:
  480.         inc   di             (* if we don't do movsb then we must *)
  481.         inc   si             (* manually increase si and di *)
  482.         dec   cx             (* and decrease the counter *)
  483.         cmp   cx,0           (* are we at the end of the row? *)
  484.         je    @@EndOfRow     (* if so, go to end of row line  *)
  485.         jmp   @@PutPixel     (* otherwise, do the next pixel  *)
  486.  
  487.         (* OK, all done, so quit *)
  488.    @@Done:
  489.         pop   ds              (* restore data segment pointer *)
  490.         pop   es
  491.    End;
  492.  
  493. And what does it mean? Basically, it's
  494.         for y:=1 to number of rows do
  495.             for x:=1 to number of pixels in a row do
  496.                 if the pixel is not black
  497.                    movsb
  498.                 else
  499.                    manually update the pointers
  500.                    (ie., move over to next pixel)
  501. In here
  502.    @@PutPixel:
  503.         mov   ax,[ds:si]         (* retrieve value in ds:si        *)
  504.         cmp   ax,0               (* is this a black (#0) pixel?    *)
  505.         je    @@SkipPixel        (* if so, skip it (goto SkipPixel *)
  506.         movsb                    (* copy cx words to the buffer    *)
  507.         loop  @@PutPixel         (* keep looping until cx=0        *)
  508. we're reading the value pointed to by ds:si (ds:si is a number like
  509. 0000:0123 while [ds:si] is a byte like the number 4 or 256) and then
  510. comparing that to 0 (the color we don't want to draw). If it's not the
  511. unwanted color, then do the old familiar movsb (if we did movsw, we'd
  512. have copied the next pixel too, whether it was the unwanted color or
  513. not). And then we go back to the PutPixel loop until we've finished
  514. copying all the data for that row (ie., when cx=0).
  515.  
  516. And if ax does hold the color we don't want?
  517.    @@SkipPixel:
  518.         inc   di             (* if we don't do movsb then we must *)
  519.         inc   si             (* manually increase si and di *)
  520.         dec   cx             (* and decrease the counter *)
  521.         cmp   cx,0           (* are we at the end of the row? *)
  522.         je    @@EndOfRow     (* if so, go to end of row line  *)
  523.         jmp   @@PutPixel     (* otherwise, do the next pixel  *)
  524. Movsb moves over to the next pixel for us and decreases the counter
  525. (cx). If we don't use movsb (we don't draw the pixel) then we have to
  526. manually update these. We also need to check cx to see if we just drew
  527. (or in this case skipped) the last pixel in that row. If not, we go to
  528. the next pixel, if so, we do the end of row routines like normal (move
  529. to the next line and reset the counter).
  530.  
  531. Needless to say, given all the tests and jumping around we do, this is
  532. not the fastest routine there is.
  533.  
  534. How I Got These Pictures
  535. ========================
  536. Well, that's about all there is to it to the tiny tutorial on tile-based
  537. graphics. I hope my spelling held out so far (it normally doesn't and
  538. I'm not going to spend the time editing this, so forgive any nonsense
  539. sentences).
  540.  
  541. But how did I get those pictures? The old question of sprite editors and
  542. the like. I used a really convulted way - draw the pictures in Deluxe
  543. Animator (my drawing program of choice) and then writing a small program
  544. that displays the reulting pcx file created by DA and moving a 32x32
  545. rectangle down to the picture I want. I then press enter and it saves
  546. that 32x32 region to disk, which I promptly rename. I will eventually
  547. write another sprite editor (I've written several already, mostly for
  548. ega and text font editors) but for now this works for me. Sorry this
  549. doesn't help to many of you out.
  550.  
  551. The icons are easy - just layer colors next to each other (like the
  552. water which is light blue with a dark blue undercoating which helps set
  553. it off). I've seen columns done in arcade games with a solid white line
  554. in the middle and just small, darker lines on either side (sounds silly
  555. but it actually looks ok). The grass was a solid green background with
  556. DA's spray paint tool used to put light green and black on top of it.
  557.  
  558. I hear some people make 256-color sprite editors, though I never saw one
  559. I liked. Many people use Autodesk Animator (which I don't own
  560. regretably but hear is nice). Others (like Sierra) just paint pictures
  561. and scan them in. If you get a drawing tool, what I do to cheat is to
  562. place a piece of see-through paper on the screen with a drawing I did by
  563. hand and then try to trace that with the mouse on the screen.
  564.  
  565. But writing a tile-editor is nice, because eventually you'll have to
  566. write a map editor (if no one's seen one, I have one hardcoded for a
  567. game I wrote once I could show around, but it let's you draw maps by
  568. picking tiles the way you normally pick colors and drawing those
  569. around with PutTile commands like those we did here).
  570.  
  571. Why Pascal
  572. ==========
  573. This demo was written in pascal because I didn't want to waste time with
  574. stupid errors that C seems to like so much. The code is just as fast or
  575. faster in turbo pascal than C, and the executable's smaller because of
  576. the way TPUs are linked in versus C libraries.
  577.  
  578. But the truth is either is pretty much the same. They use almost the
  579. same commands and nothing in this demo is so wierd that couldn't be
  580. translated, line for line, to C. For example, the hard stuff (assembler)
  581. was Asm stuff... End; in pascal and is asm { stuff } in C. Not so bad.
  582. Given that I'll probably rewrite parts of this in C++ (or at least a
  583. game using many of these constructs) I'm glad it'll port nicely. Truth
  584. is, if you know a language and want a quick game, write with what you
  585. know. Last I heard, the fastest compiler was a basic compiler anyway
  586. (made by some European or Australian company - heard the documentation is
  587. pretty bad though). Just do what feels good - the fast parts are in
  588. assembler and the rest doesn't hurt your performance no matter what
  589. langauge you use. Cobol with these routines would make a nice fast game.
  590.  
  591. Why I Wrote This
  592. ================
  593. Three years ago (Spring 1991) my roommate, seeing I was depressed with
  594. my major (journalism), talked me into using a computer, signing up for
  595. beggining programming. 3 years wasn't that long ago, so I remember quite
  596. clearly feeling like maybe I was a bright guy and still had no clue how
  597. to do what I wanted. Computers frustrated me at every turn.
  598.  
  599. Writing computer games isn't a matter of intelligence, it's a matter of
  600. experience. If you're a bright person, there's no reason why you
  601. shouldn't have some help learning the basics, and no one should make you
  602. feel dumb (as I often did) for not knowing.
  603.  
  604. I wish I had something like this to help me rather than all those
  605. (expensive) books I ended up buying that only briefly touched on what I
  606. wanted to know. I am indebted to people like Josh Jensen, Themie, Vic
  607. Putz, Dr Cat and others who helped me out when I was completely lost. I
  608. certainly wouldn't know anything if it hadn't been for other people's
  609. help. Hopefully I can help pay them back by passing on what meager
  610. amount I know.
  611.  
  612. And by the way, I never did changes majors to computer science. It took
  613. me 5 semesters to pass calculus, at which point I figured I'd cop out
  614. and get a business degree. And now I wear a tie. Friends don't let
  615. friends wear ties.
  616.  
  617.                                                 - baylor
  618.